+++ /dev/null
-// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
-
-const St = imports.gi.St;
-const Shell = imports.gi.Shell;
-const Lang = imports.lang;
-const Signals = imports.signals;
-const Search = imports.ui.search;
-
-const THUMBNAIL_ICON_MARGIN = 2;
-
-const DocInfo = new Lang.Class({
- Name: 'DocInfo',
-
- _init : function(recentInfo) {
- this.recentInfo = recentInfo;
- // We actually used get_modified() instead of get_visited()
- // here, as GtkRecentInfo doesn't updated get_visited()
- // correctly. See http://bugzilla.gnome.org/show_bug.cgi?id=567094
- this.timestamp = recentInfo.get_modified();
- this.name = recentInfo.get_display_name();
- this._lowerName = this.name.toLowerCase();
- this.uri = recentInfo.get_uri();
- this.mimeType = recentInfo.get_mime_type();
- },
-
- createIcon : function(size) {
- return St.TextureCache.get_default().load_recent_thumbnail(size, this.recentInfo);
- },
-
- launch : function(workspaceIndex) {
- Shell.DocSystem.get_default().open(this.recentInfo, workspaceIndex);
- },
-
- matchTerms: function(terms) {
- let mtype = Search.MatchType.NONE;
- for (let i = 0; i < terms.length; i++) {
- let term = terms[i];
- let idx = this._lowerName.indexOf(term);
- if (idx == 0) {
- mtype = Search.MatchType.PREFIX;
- } else if (idx > 0) {
- if (mtype == Search.MatchType.NONE)
- mtype = Search.MatchType.SUBSTRING;
- } else {
- return Search.MatchType.NONE;
- }
- }
- return mtype;
- }
-});
-
-var docManagerInstance = null;
-
-function getDocManager() {
- if (docManagerInstance == null)
- docManagerInstance = new DocManager();
- return docManagerInstance;
-}
-
-/**
- * DocManager wraps the DocSystem, primarily to expose DocInfo objects.
- */
-const DocManager = new Lang.Class({
- Name: 'DocManager',
-
- _init: function() {
- this._docSystem = Shell.DocSystem.get_default();
- this._infosByTimestamp = [];
- this._infosByUri = {};
- this._docSystem.connect('changed', Lang.bind(this, this._reload));
- this._reload();
- },
-
- _reload: function() {
- let docs = this._docSystem.get_all();
- this._infosByTimestamp = [];
- this._infosByUri = {};
- for (let i = 0; i < docs.length; i++) {
- let recentInfo = docs[i];
-
- let docInfo = new DocInfo(recentInfo);
- this._infosByTimestamp.push(docInfo);
- this._infosByUri[docInfo.uri] = docInfo;
- }
- this.emit('changed');
- },
-
- getTimestampOrderedInfos: function() {
- return this._infosByTimestamp;
- },
-
- getInfosByUri: function() {
- return this._infosByUri;
- },
-
- lookupByUri: function(uri) {
- return this._infosByUri[uri];
- },
-
- queueExistenceCheck: function(count) {
- return this._docSystem.queue_existence_check(count);
- },
-
- _searchDocs: function(items, terms) {
- let multiplePrefixMatches = [];
- let prefixMatches = [];
- let multipleSubtringMatches = [];
- let substringMatches = [];
- for (let i = 0; i < items.length; i++) {
- let item = items[i];
- let mtype = item.matchTerms(terms);
- if (mtype == Search.MatchType.MULTIPLE_PREFIX)
- multiplePrefixMatches.push(item.uri);
- else if (mtype == Search.MatchType.PREFIX)
- prefixMatches.push(item.uri);
- else if (mtype == Search.MatchType.MULTIPLE_SUBSTRING)
- multipleSubtringMatches.push(item.uri);
- else if (mtype == Search.MatchType.SUBSTRING)
- substringMatches.push(item.uri);
- }
- return multiplePrefixMatches.concat(prefixMatches.concat(multipleSubtringMatches.concat(substringMatches)));
- },
-
- initialSearch: function(terms) {
- return this._searchDocs(this._infosByTimestamp, terms);
- },
-
- subsearch: function(previousResults, terms) {
- return this._searchDocs(previousResults.map(Lang.bind(this,
- function(url) {
- return this._infosByUri[url];
- })), terms);
- }
-});
-
-Signals.addSignalMethods(DocManager.prototype);
+++ /dev/null
-/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
-
-#include "config.h"
-
-#include "shell-doc-system.h"
-
-#include "shell-global.h"
-
-
-/**
- * SECTION:shell-doc-system
- * @short_description: Track recently used documents
- *
- * Wraps #GtkRecentManager, caching recently used document information, and adds
- * APIs for asynchronous queries.
- */
-enum {
- CHANGED,
- DELETED,
- LAST_SIGNAL
-};
-
-static guint signals[LAST_SIGNAL] = { 0 };
-
-struct _ShellDocSystemPrivate {
- GtkRecentManager *manager;
- GHashTable *infos_by_uri;
- GSList *infos_by_timestamp;
-
- guint idle_recent_changed_id;
-
- GHashTable *deleted_infos;
- guint idle_emit_deleted_id;
-};
-
-G_DEFINE_TYPE(ShellDocSystem, shell_doc_system, G_TYPE_OBJECT);
-
-/**
- * shell_doc_system_get_all:
- * @system: A #ShellDocSystem
- *
- * Returns the currently cached set of recent files. Recent files are read initially
- * from the underlying #GtkRecentManager, and updated when it changes.
- * This function does not perform I/O.
- *
- * Returns: (transfer none) (element-type GtkRecentInfo): Cached recent file infos
- */
-GSList *
-shell_doc_system_get_all (ShellDocSystem *self)
-{
- return self->priv->infos_by_timestamp;
-}
-
-/**
- * shell_doc_system_lookup_by_uri:
- * @system: A #ShellDocSystem
- * @uri: URI
- *
- * Returns: (transfer none): Recent file info corresponding to given @uri
- */
-GtkRecentInfo *
-shell_doc_system_lookup_by_uri (ShellDocSystem *system,
- const char *uri)
-{
- return g_hash_table_lookup (system->priv->infos_by_uri, uri);
-}
-
-static gboolean
-shell_doc_system_idle_emit_deleted (gpointer data)
-{
- ShellDocSystem *self = SHELL_DOC_SYSTEM (data);
- GHashTableIter iter;
- gpointer key, value;
-
- self->priv->idle_emit_deleted_id = 0;
-
- g_hash_table_iter_init (&iter, self->priv->deleted_infos);
-
- while (g_hash_table_iter_next (&iter, &key, &value))
- {
- GtkRecentInfo *info = key;
- g_signal_emit (self, signals[DELETED], 0, info);
- }
-
- g_signal_emit (self, signals[CHANGED], 0);
-
- return FALSE;
-}
-
-typedef struct {
- ShellDocSystem *self;
- GtkRecentInfo *info;
-} ShellDocSystemRecentQueryData;
-
-static void
-on_recent_file_query_result (GObject *source,
- GAsyncResult *result,
- gpointer user_data)
-{
- ShellDocSystemRecentQueryData *data = user_data;
- ShellDocSystem *self = data->self;
- GError *error = NULL;
- GFileInfo *fileinfo;
-
- fileinfo = g_file_query_info_finish (G_FILE (source), result, &error);
- if (fileinfo)
- g_object_unref (fileinfo);
- /* This is a strict error check; we don't want to cause recent files to
- * vanish for anything potentially transient.
- */
- if (error != NULL && error->domain == G_IO_ERROR && error->code == G_IO_ERROR_NOT_FOUND)
- {
- self->priv->infos_by_timestamp = g_slist_remove (self->priv->infos_by_timestamp, data->info);
- g_hash_table_remove (self->priv->infos_by_uri, gtk_recent_info_get_uri (data->info));
-
- g_hash_table_insert (self->priv->deleted_infos, gtk_recent_info_ref (data->info), NULL);
-
- if (self->priv->idle_emit_deleted_id == 0)
- self->priv->idle_emit_deleted_id = g_timeout_add (0, shell_doc_system_idle_emit_deleted, self);
- }
- g_clear_error (&error);
-
- gtk_recent_info_unref (data->info);
- g_free (data);
-}
-
-/**
- * shell_doc_system_queue_existence_check:
- * @system: A #ShellDocSystem
- * @n_items: Count of items to check for existence, starting from most recent
- *
- * Asynchronously start a check of a number of recent file for existence;
- * any deleted files will be emitted from the #ShellDocSystem::deleted
- * signal. Note that this function ignores non-local files; they
- * will simply always appear to exist (until they are removed from
- * the recent file list manually).
- *
- * The intent of this function is to be called after a #ShellDocSystem::changed
- * signal has been emitted, and a display has shown a subset of those files.
- */
-void
-shell_doc_system_queue_existence_check (ShellDocSystem *self,
- guint n_items)
-{
- GSList *iter;
- guint i;
-
- for (i = 0, iter = self->priv->infos_by_timestamp; i < n_items && iter; i++, iter = iter->next)
- {
- GtkRecentInfo *info = iter->data;
- const char *uri;
- GFile *file;
- ShellDocSystemRecentQueryData *data;
-
- if (!gtk_recent_info_is_local (info))
- continue;
-
- data = g_new0 (ShellDocSystemRecentQueryData, 1);
- data->self = self;
- data->info = gtk_recent_info_ref (info);
-
- uri = gtk_recent_info_get_uri (info);
- file = g_file_new_for_uri (uri);
-
- g_file_query_info_async (file, "standard::type", G_FILE_QUERY_INFO_NONE,
- G_PRIORITY_DEFAULT, NULL, on_recent_file_query_result, data);
- g_object_unref (file);
- }
-}
-
-static int
-sort_infos_by_timestamp_descending (gconstpointer a,
- gconstpointer b)
-{
- GtkRecentInfo *info_a = (GtkRecentInfo*)a;
- GtkRecentInfo *info_b = (GtkRecentInfo*)b;
- time_t modified_a, modified_b;
-
- modified_a = gtk_recent_info_get_modified (info_a);
- modified_b = gtk_recent_info_get_modified (info_b);
-
- return modified_b - modified_a;
-}
-
-static gboolean
-idle_handle_recent_changed (gpointer data)
-{
- ShellDocSystem *self = SHELL_DOC_SYSTEM (data);
- GList *items, *iter;
-
- self->priv->idle_recent_changed_id = 0;
-
- g_hash_table_remove_all (self->priv->deleted_infos);
- g_hash_table_remove_all (self->priv->infos_by_uri);
- g_slist_free (self->priv->infos_by_timestamp);
- self->priv->infos_by_timestamp = NULL;
-
- items = gtk_recent_manager_get_items (self->priv->manager);
- for (iter = items; iter; iter = iter->next)
- {
- GtkRecentInfo *info = iter->data;
- const char *uri = gtk_recent_info_get_uri (info);
-
- /* uri is owned by the info */
- g_hash_table_insert (self->priv->infos_by_uri, (char*) uri, info);
-
- self->priv->infos_by_timestamp = g_slist_prepend (self->priv->infos_by_timestamp, info);
- }
- g_list_free (items);
-
- self->priv->infos_by_timestamp = g_slist_sort (self->priv->infos_by_timestamp, sort_infos_by_timestamp_descending);
-
- g_signal_emit (self, signals[CHANGED], 0);
-
- return FALSE;
-}
-
-static void
-shell_doc_system_on_recent_changed (GtkRecentManager *manager,
- ShellDocSystem *self)
-{
- if (self->priv->idle_recent_changed_id != 0)
- return;
- self->priv->idle_recent_changed_id = g_timeout_add (0, idle_handle_recent_changed, self);
-}
-
-/**
- * shell_doc_system_open:
- * @system: A #ShellDocSystem
- * @info: A #GtkRecentInfo
- * @workspace: Open on this workspace, or -1 for default
- *
- * Launch the default application associated with the mime type of
- * @info, using its uri.
- */
-void
-shell_doc_system_open (ShellDocSystem *system,
- GtkRecentInfo *info,
- int workspace)
-{
- GFile *file;
- GAppInfo *app_info;
- gboolean needs_uri;
- GAppLaunchContext *context;
-
- context = shell_global_create_app_launch_context (shell_global_get ());
- if (workspace != -1)
- gdk_app_launch_context_set_desktop ((GdkAppLaunchContext *)context, workspace);
-
- file = g_file_new_for_uri (gtk_recent_info_get_uri (info));
- needs_uri = g_file_get_path (file) == NULL;
- g_object_unref (file);
-
- app_info = g_app_info_get_default_for_type (gtk_recent_info_get_mime_type (info), needs_uri);
- if (app_info != NULL)
- {
- GList *uris;
- uris = g_list_prepend (NULL, (gpointer)gtk_recent_info_get_uri (info));
- g_app_info_launch_uris (app_info, uris, context, NULL);
- g_list_free (uris);
- }
- else
- {
- char *app_name;
- const char *app_exec;
- char *app_exec_quoted;
- guint count;
- time_t time;
-
- app_name = gtk_recent_info_last_application (info);
- if (gtk_recent_info_get_application_info (info, app_name, &app_exec, &count, &time))
- {
- GRegex *regex;
-
- /* TODO: Change this once better support for creating
- GAppInfo is added to GtkRecentInfo, as right now
- this relies on the fact that the file uri is
- already a part of appExec, so we don't supply any
- files to app_info.launch().
-
- The 'command line' passed to
- create_from_command_line is allowed to contain
- '%<something>' macros that are expanded to file
- name / icon name, etc, so we need to escape % as %%
- */
-
- regex = g_regex_new ("%", 0, 0, NULL);
- app_exec_quoted = g_regex_replace (regex, app_exec, -1, 0, "%%", 0, NULL);
- g_regex_unref (regex);
-
- app_info = g_app_info_create_from_commandline (app_exec_quoted, NULL, 0, NULL);
- g_free (app_exec_quoted);
-
- /* The point of passing an app launch context to
- launch() is mostly to get startup notification and
- associated benefits like the app appearing on the
- right desktop; but it doesn't really work for now
- because with the way we create the appInfo we
- aren't reading the application's desktop file, and
- thus don't find the StartupNotify=true in it. So,
- despite passing the app launch context, no startup
- notification occurs.
- */
- g_app_info_launch (app_info, NULL, context, NULL);
- }
-
- g_free (app_name);
- }
-
- g_object_unref (context);
-}
-
-static void
-shell_doc_system_class_init(ShellDocSystemClass *klass)
-{
- GObjectClass *gobject_class = (GObjectClass *)klass;
-
- signals[CHANGED] =
- g_signal_new ("changed",
- SHELL_TYPE_DOC_SYSTEM,
- G_SIGNAL_RUN_LAST,
- 0,
- NULL, NULL, NULL,
- G_TYPE_NONE, 0);
-
- signals[DELETED] =
- g_signal_new ("deleted",
- SHELL_TYPE_DOC_SYSTEM,
- G_SIGNAL_RUN_LAST,
- 0,
- NULL, NULL, NULL,
- G_TYPE_NONE, 1, GTK_TYPE_RECENT_INFO);
-
- g_type_class_add_private (gobject_class, sizeof (ShellDocSystemPrivate));
-}
-
-static void
-shell_doc_system_init (ShellDocSystem *self)
-{
- ShellDocSystemPrivate *priv;
-
- self->priv = priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
- SHELL_TYPE_DOC_SYSTEM,
- ShellDocSystemPrivate);
- self->priv->manager = gtk_recent_manager_get_default ();
-
- self->priv->deleted_infos = g_hash_table_new_full (NULL, NULL, (GDestroyNotify)gtk_recent_info_unref, NULL);
- self->priv->infos_by_uri = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, (GDestroyNotify)gtk_recent_info_unref);
-
- g_signal_connect (self->priv->manager, "changed", G_CALLBACK(shell_doc_system_on_recent_changed), self);
- shell_doc_system_on_recent_changed (self->priv->manager, self);
-}
-
-/**
- * shell_doc_system_get_default:
- *
- * Return Value: (transfer none): The global #ShellDocSystem singleton
- */
-ShellDocSystem *
-shell_doc_system_get_default ()
-{
- static ShellDocSystem *instance = NULL;
-
- if (instance == NULL)
- instance = g_object_new (SHELL_TYPE_DOC_SYSTEM, NULL);
-
- return instance;
-}